package com.shiguiwu.springmybatis.spring.aop.principle;

import lombok.Getter;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AdvisorChainFactory;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.framework.DefaultAdvisorChainFactory;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.target.EmptyTargetSource;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 *
 *
 * 上面几个类有几个结论，这里说一下。
 * 1. 配置中添加的Advice对象最终都会被转换为DefaultPointcutAdvisor对象，此时
 * DefaultPointcutAdvisor未指定pointcut，大家可以去看一下DefaultPointcutAdvisor中pointcut有
 * 个默认值，默认会匹配任意类的任意方法。
 * 2. 当配置被冻结的时候，即frozen为true的时，此时配置中的Advisor列表是不允许修改的。
 * 3. 上面的 getInterceptorsAndDynamicInterceptionAdvice 方法，通过代理调用目标方法的时
 * 候，最后需要通过方法和目标类的类型，从当前配置中会获取匹配的方法拦截器列表，获取方法拦
 * 截器列表是由 AdvisorChainFactory 负责的。
 * getInterceptorsAndDynamicInterceptionAdvice 会在调用代理的方法时会执行，稍后在执行
 * 阶段会详解。
 * 4. 目标方法和其关联的方法拦截器列表会被缓存在 methodCache 中，当顾问列表有变化的时候，
 * methodCache 缓存会被清除。
 * @description: 自己的代理类
 *
 * @author: stone
 * @date: Created by 2021/6/3 16:55
 * @version: 1.0.0
 * @pakeage: com.shiguiwu.springmybatis.spring.aop.principle
 */
public class MyAdvisedSupport extends ProxyConfig implements Advised {


    public static final TargetSource EMPTY_TARGET_SOURCE =
            EmptyTargetSource.INSTANCE;
    public TargetSource targetSource = EMPTY_TARGET_SOURCE;
    /**
     * 建议器是否已经针对特定的目标类进行筛选
     */
    private boolean preFiltered = false;
    /**
     * 调用链工厂，用来获取目标方法的调用链
     */
    @Getter
    public AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();
    /**
     * 方法调用链缓存：以方法为键，以顾问链表为值的缓存。
     */
    private transient Map<MethodCacheKey, List<Object>> methodCache;
    //代理对象需要实现的接口列表。保存在列表中以保持注册的顺序，以创建具有指定接口顺序的JDK代理。
    private List<Class<?>> interfaces = new ArrayList<>();
    //配置的顾问列表。所有添加的Advise对象都会被包装为Advisor对象
    private List<Advisor> advisors = new ArrayList<>();
    //数组更新了对advisor列表的更改，这更容易在内部操作。
    private Advisor[] advisorArray = new Advisor[0];

    //无参构造方法
    public MyAdvisedSupport() {
        this.methodCache = new ConcurrentHashMap<>(32);
    }

    //有参构造方法，参数为：代理需要实现的接口列表
    public MyAdvisedSupport(Class<?>... interfaces) {
        this();
        this.setInterfaces(interfaces);

    }

    //设置目标对象
    public void setTarget(Object target) {
        this.setTargetSource(new SingletonTargetSource(target));

    }


    //todo 此方法先忽略，用来为目标类引入接口的
    private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
        advisor.validateInterfaces();
        // If the advisor passed validation, we can make the change.
        Class<?>[] ifcs = advisor.getInterfaces();
        for (Class<?> ifc : ifcs) {
            addInterface(ifc);
        }
    }

    //指定的位置添加顾问
    private void addAdvisorInternal(int pos, Advisor advisor) throws
            AopConfigException {
        Assert.notNull(advisor, "Advisor must not be null");
        if (isFrozen()) {
            throw new AopConfigException("Cannot add advisor: Configuration is frozen.");
        }
        if (pos > this.advisors.size()) {
            throw new IllegalArgumentException(
                    "Illegal position " + pos + " in advisor list with size " +
                            this.advisors.size());
        }
        this.advisors.add(pos, advisor);
        updateAdvisorArray();
        adviceChanged();
    }

    //将advisorArray和advisors保持一致
    protected final void updateAdvisorArray() {
        this.advisorArray = this.advisors.toArray(new Advisor[0]);
    }

    //获取顾问列表
    protected final List<Advisor> getAdvisorsInternal() {
        return this.advisors;
    }

    //设置被代理的目标类
    public void setTargetClass(Class<?> targetClass) {
        this.targetSource = EmptyTargetSource.forClass(targetClass);
    }

    //获取被代理的目标类型
    @Override
    public Class<?> getTargetClass() {
        return this.targetSource.getTargetClass();
    }

    //设置代理对象需要实现的接口
    public void setInterfaces(Class<?>... interfaces) {
        Assert.notNull(interfaces, "Interfaces must not be null");
        this.interfaces.clear();
        for (Class<?> ifc : interfaces) {
            addInterface(ifc);
        }

    }

    /**
     * 为代理对象添加需要实现的接口
     *
     * @param intf
     */
    public void addInterface(Class<?> intf) {
        Assert.notNull(intf, "Interface must not be null");
        if (!intf.isInterface()) {
            throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface");
        } else {
            if (!this.interfaces.contains(intf)) {
                this.interfaces.add(intf);
                this.adviceChanged();
            }

        }
    }

    /**
     * 通知更改时调用，会清空当前方法调用链缓存
     */
    protected void adviceChanged() {
        this.methodCache.clear();
    }

    /**
     *   * 设置顾问链工厂，当调用目标方法的时候，需要获取这个方法上匹配的Advisor列表，
     * 获取目标方法上匹配的Advisor列表的功能就是AdvisorChainFactory来负责的
     */
    public void setAdvisorChainFactory(AdvisorChainFactory advisorChainFactory) {
        Assert.notNull(advisorChainFactory, "advisorChainFactory is must not null");
        this.advisorChainFactory = advisorChainFactory;
    }

    //移除代理对象需要实现的接口
    public boolean removeInterface(Class<?> intf) {
        return this.interfaces.remove(intf);
    }

    /**
     * 获取代理对象需要实现的接口列表
     *
     * @return
     */
    @Override
    public Class<?>[] getProxiedInterfaces() {
        return ClassUtils.toClassArray(this.interfaces);
    }

    /**
     * 判断代理对象是否需要实现某个接口
     *
     * @param aClass
     * @return
     */
    @Override
    public boolean isInterfaceProxied(Class<?> aClass) {
        for (Class<?> proxyIntf : this.interfaces) {
            if (aClass.isAssignableFrom(proxyIntf)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void setTargetSource(@Nullable TargetSource targetSource) {
        this.targetSource = (targetSource == null ? EMPTY_TARGET_SOURCE : targetSource);
    }


    //获取被代理的目标源
    @Override
    public TargetSource getTargetSource() {
        return this.targetSource;
    }


    /**
     * todo
     * 设置此代理配置是否经过预筛选，这个什么意思呢：通过目标方法调用代理的时候，
     * 需要通过匹配的方式获取这个方法上的调用链列表，查找过程需要2个步骤：
     * 第一步：类是否匹配，第二步：方法是否匹配，当这个属性为true的时候，会直接跳过第一步，这个
     * 懂了不
     */
    @Override
    public void setPreFiltered(boolean preFiltered) {
        this.preFiltered = preFiltered;
    }

    // 返回preFiltered
    @Override
    public boolean isPreFiltered() {
        return this.preFiltered;
    }


    /**
     * 获取所有的顾问列表
     *
     * @return
     */
    @Override
    public Advisor[] getAdvisors() {
        return this.advisorArray;
    }

    /**
     * 添加顾问
     *
     * @param advisor
     * @throws AopConfigException
     */
    @Override
    public void addAdvisor(Advisor advisor) throws AopConfigException {
        int pos = this.advisors.size();
        this.addAdvisor(pos, advisor);
    }

    /**
     * 指定的位置添加顾问
     *
     * @param pos
     * @param advisor
     * @throws AopConfigException
     */
    @Override
    public void addAdvisor(int pos, Advisor advisor) throws AopConfigException {
        // todo 这块先忽略，以后讲解
        if (advisor instanceof IntroductionAdvisor) {
            validateIntroductionAdvisor((IntroductionAdvisor) advisor);
        }
        addAdvisorInternal(pos, advisor);
    }

    @Override
    public boolean removeAdvisor(Advisor advisor) {
        int index = indexOf(advisor);
        if (index == -1) {
            return false;
        }
        removeAdvisor(index);
        return true;
    }

    @Override
    public void removeAdvisor(int index) throws AopConfigException {
        //当配置如果是冻结状态，是不允许对顾问进行修改的，否则会抛出异常
        if (isFrozen()) {
            throw new AopConfigException("Cannot remove Advisor: Configuration is frozen.");
        }
        if (index < 0 || index > this.advisors.size() - 1) {
            throw new AopConfigException("Advisor index " + index + " is out of bounds:" +
                    "This configuration only has " + this.advisors.size() + " advisors. ");
        }
        //移除advisors中的顾问
        Advisor advisor = this.advisors.remove(index);
        if (advisor instanceof IntroductionAdvisor) {
            IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
            // We need to remove introduction interfaces.
            for (Class<?> ifc : ia.getInterfaces()) {
                removeInterface(ifc);
            }
        }
        //更新advisorArray
        updateAdvisorArray();
        //通知已改变，内部会清除方法调用链缓存信息。
        adviceChanged();
    }

    @Override
    public int indexOf(Advisor advisor) {
        Assert.notNull(advisor, "Advisor must not be null");
        return this.advisors.indexOf(advisor);
    }

    @Override
    public boolean replaceAdvisor(Advisor replaceAdvisor, Advisor targetAdvisor) throws AopConfigException {
        Assert.notNull(replaceAdvisor, "Advisor a must not be null");
        Assert.notNull(targetAdvisor, "Advisor b must not be null");
        int index = indexOf(replaceAdvisor);
        if (index == -1) {
            return false;
        }
        removeAdvisor(index);
        addAdvisor(index, targetAdvisor);
        return true;
    }

    /**
     * 添加通知
     * @param advice
     * @throws AopConfigException
     */
    @Override
    public void addAdvice(Advice advice) throws AopConfigException {
        int pos = this.advisors.size();
        this.addAdvice(pos, advice);
    }

    /**
     * 添加指定位置的通知
     * @param index
     * @param advice
     * @throws AopConfigException
     */
    @Override
    public void addAdvice(int index, Advice advice) throws AopConfigException {
        //此处会将advice通知包装为DefaultPointcutAdvisor类型的Advisor
        addAdvisor(index, new DefaultPointcutAdvisor(advice));
    }

    /**
     * 移除通知
     * @param advice
     * @return
     */
    @Override
    public boolean removeAdvice(Advice advice) {
        int index = indexOf(advice);
        if (index == -1) {
            return false;
        }
        removeAdvisor(index);
        return true;
    }

    /**
     *
     * @param advice
     * @return
     */
    @Override
    public int indexOf(Advice advice) {
        Assert.notNull(advice, "Advice must not be null");

        for (int i = 0; i < this.advisors.size(); i++) {
            Advisor advisor = this.advisors.get(i);
            if (advisor.getAdvice() == advice) {
                return i;
            }
        }
        return -1;
    }

    //是否包含某个通知
    public boolean adviceIncluded(@Nullable Advice advice) {
        if (advice != null) {
            for (Advisor advisor : this.advisors) {
                if (advisor.getAdvice() == advice) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 获取某种类型的通知数量
     * @param adviceClass
     * @return
     */
    public int countAdvicesOfType(Class<?> adviceClass) {
        int count = 0;
        if (adviceClass == null) {
            return count;
        }
        long count1 = this.advisors.stream().filter(e -> adviceClass.isInstance(e.getAdvice())).count();
        count = (int) count1;
        return count;
    }

    /**
     * 基于当前配置，获取给定方法的方法调用链列表（即
     * org.aopalliance.intercept.MethodInterceptor对象列表）
     *
     * @param method
     * @param targetClass
     * @return
     */
    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) {
        MethodCacheKey methodCacheKey = new MethodCacheKey(method);
        //会先尝试从还中获取，如果获取不到，会从advisorChainFactory中获取，然后将其丢到缓存中
        return this.methodCache.computeIfAbsent(methodCacheKey, m -> this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                this, method, targetClass));
    }

    @Override
    public String toProxyConfigString() {
        return this.toString();
    }



    /**
     * 将other中的配置信息复制到当前对象中
     * @param other
     * @param targetSource
     * @param advisors
     */
    protected void copyConfigurationFrom(MyAdvisedSupport other, TargetSource
            targetSource, List<Advisor> advisors) {
        copyFrom(other);
        this.targetSource = targetSource;
        this.advisorChainFactory = other.advisorChainFactory;
        this.interfaces = new ArrayList<>(other.interfaces);
        for (Advisor advisor : advisors) {
            if (advisor instanceof IntroductionAdvisor) {
                validateIntroductionAdvisor((IntroductionAdvisor) advisor);
            }
            Assert.notNull(advisor, "Advisor must not be null");
            this.advisors.add(advisor);
        }
        updateAdvisorArray();
        adviceChanged();
    }

    //构建此AdvisedSupport的仅配置副本，替换TargetSource。

    /**
     * 构建此AdvisedSupport的仅配置副本，替换TargetSource。
     * @return
     */
    MyAdvisedSupport getConfigurationOnlyCopy() {
        MyAdvisedSupport copy = new MyAdvisedSupport();
        copy.copyFrom(this);
        copy.targetSource = EmptyTargetSource.forClass(getTargetClass(),
                getTargetSource().isStatic());
        copy.advisorChainFactory = this.advisorChainFactory;
        copy.interfaces = this.interfaces;
        copy.advisors = this.advisors;
        copy.updateAdvisorArray();
        return copy;
    }





    private static final class MethodCacheKey implements Comparable<MethodCacheKey> {
        private final Method method;
        private final int hashCode;

        public MethodCacheKey(Method method) {
            this.method = method;
            this.hashCode = method.hashCode();
        }

        public boolean equals(@Nullable Object other) {
            return this == other || other instanceof MyAdvisedSupport.MethodCacheKey && this.method == ((MyAdvisedSupport.MethodCacheKey) other).method;
        }

        public int hashCode() {
            return this.hashCode;
        }

        public String toString() {
            return this.method.toString();
        }

        public int compareTo(MethodCacheKey other) {
            int result = this.method.getName().compareTo(other.method.getName());
            if (result == 0) {
                result = this.method.toString().compareTo(other.method.toString());
            }

            return result;
        }
    }

}
